package com.peng.test.meishirobot;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.peng.utils.JacksonUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * MeishiRobot
 * 美事机器人工具类，用来发送美事消息到指定群聊。
 * 发送的时候需要指定群id（groupId），这个参数可以通过 queryRobotGroups() 来获取
 * <p>
 * 下面是一个curl发送消息的demo
 * <p>
 * curl -XPOST 'http://openapi.mism.58dns.org/msg/sendGroupMsgNew' \
 * -H 'Content-Type: application/json' \
 * -d '{
 * "msRequestId": {
 * "appId": "ZhaopinEnterpriseRobot",
 * "reqTime": 1682236893527,
 * "sign": "ca83e2170150e04378f9f5e465286916",
 * "trace": "ZhaopinEnterpriseRobot:1682236893527:925155",
 * "random": "925155"
 * },
 * "msg": {
 * "content": "{\"msg\":\"@卢鹏 应该是谁围巾吧，落在A5的6楼湿区了，\",\"type\":\"text\"}",
 * "senderId": "MIS_ROBOT_ZhaopinEnterpriseRobot",
 * "showType": "text",
 * "toId": "251586",
 * "atUsers": [
 * {
 * "oa": "lupeng10",
 * "pos_start": 0,
 * "pos_end": 3,
 * "name": "卢鹏"
 * }
 * ]
 * }
 * }'
 *
 * @author: lupeng10
 * @create: 2023-04-23 20:21
 */
@Slf4j
public class MeishiRobot {

    // 发送群消息的接口
    private static final String SEND_GROUP_MSG_URL = "http://openapi.mism.58dns.org/msg/sendGroupMsgNew";

    // 获取群内成员
    private static final String QUERY_GROUP_MEMBER_LIST_URL = "http://openapi.mism.58dns.org/group/getMemberListByGid";

    // 查询机器人所在的群id
    private static final String QUERY_GROUPS_BY_UID = "http://openapi.mism.58dns.org/group/getGroupsByUid";

    // 通过OAname 查询真实的名字加个缓存
    private final LoadingCache<Pair<String, String>, String> OANAME_TO_REAL_NAME_CACHE = CacheBuilder.newBuilder()
            .maximumSize(10000)
            //设置写缓存后n秒钟过期
            .expireAfterWrite(60, TimeUnit.SECONDS)
            //设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
            .expireAfterAccess(17, TimeUnit.SECONDS)
            //只阻塞当前数据加载线程，其他线程返回旧值
            .refreshAfterWrite(13, TimeUnit.SECONDS)
            .build(new CacheLoader<>() {
                @Override
                public String load(@NotNull Pair<String, String> key) {
                    return getRealNameByOaName(key.getLeft(), key.getRight());
                }
            });

    // http client 通过http接口发送消息
    private static final CloseableHttpClient HTTP_CLIENT;

    static {
        RequestConfig requestConfig = RequestConfig.custom()
                // 从连接池获取可用连接的超时时间，单位毫秒
                .setConnectionRequestTimeout(10000, TimeUnit.MILLISECONDS)
                // 链接超时时间
                .setConnectTimeout(5000, TimeUnit.MILLISECONDS)
                // 响应超时时间
                .setResponseTimeout(5000, TimeUnit.MILLISECONDS)
                .build();

        HTTP_CLIENT = HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .build();
    }

    private String appId;
    private String secret;
    private int encryption;
    private String senderId;


    private MeishiRobot() {
    }

    public MeishiRobot(String appId, String secret, int encryption, String senderId) {
        this.appId = appId;
        this.secret = secret;
        this.encryption = encryption;
        this.senderId = senderId;
    }

    /**
     * 发送text类型的文本
     *
     * @param groupId       群id
     * @param textContent   发送的内容
     * @param atUserOaNames 需要@的用户列表，oa name
     * @return Response 发送结果
     */
    public Response sendTextMsg(String groupId, String textContent, Collection<String> atUserOaNames) {
        try {
            Request request = buildRequest(groupId, textContent, atUserOaNames);
            String resStr = sendMsgByHttpRequest(request);
            return JacksonUtil.fromJson(resStr, Response.class);
        } catch (Exception e) {
            log.error("sendTextMsg error", e);
        }
        return new Response();
    }

    /**
     * 查询机器人所在的群id
     */
    public String queryRobotGroups() {
        try {
            Map<String, Object> request = new HashMap<>();
            request.put("userId", this.senderId);
            request.put("requestId", getMsRequestId(this.appId, this.encryption, this.secret));

            HttpPost httpPost = new HttpPost(QUERY_GROUPS_BY_UID);
            httpPost.addHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
            httpPost.setEntity(new StringEntity(JacksonUtil.toJson(request), ContentType.APPLICATION_JSON));
            CloseableHttpResponse response = HTTP_CLIENT.execute(httpPost);
            return EntityUtils.toString(response.getEntity());
        } catch (Exception e) {
            log.error("queryRobotGroups error", e);
        }
        return null;
    }

    private String sendMsgByHttpRequest(Request request) throws IOException, ParseException {
        HttpPost httpPost = new HttpPost(SEND_GROUP_MSG_URL);
        httpPost.addHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
        httpPost.setEntity(new StringEntity(JacksonUtil.toJson(request), ContentType.APPLICATION_JSON));
        CloseableHttpResponse response = HTTP_CLIENT.execute(httpPost);
        return EntityUtils.toString(response.getEntity());
    }

    /**
     * 构建 request
     */
    private Request buildRequest(String groupId, String textContent, Collection<String> atUserOaNames) throws Exception {
        Request request = new Request();
        request.setMsRequestId(getMsRequestId(this.appId, this.encryption, this.secret));

        Msg msg = new Msg();
        msg.setSenderId(this.senderId);
        msg.setShowType("text");
        msg.setToId(groupId);
        // atusers
        if (CollectionUtils.isNotEmpty(atUserOaNames)) {
            StringBuilder contentPrefix = new StringBuilder();
            List<AtUser> atUsers = new ArrayList<>();
            int index = 0;
            for (String oaName : atUserOaNames) {
                // String realName = this.getRealNameByOaName(oaName, groupId);
                String realName = getRealNameByOaNameWithCache(oaName, groupId);
                if (StringUtils.isNotEmpty(realName)) {
                    String atUserDisplayText = "@" + realName;
                    contentPrefix.append(atUserDisplayText);

                    atUsers.add(new AtUser(oaName, index, index + atUserDisplayText.length(), realName));
                    index += atUserDisplayText.length();
                }
            }
            contentPrefix.append(" ");
            msg.setAtUsers(atUsers);
            textContent = contentPrefix + textContent;
        }

        //context
        Map<String, String> msgContentMsg = new HashMap<>();
        msgContentMsg.put("type", "text");
        msgContentMsg.put("msg", textContent);
        msg.setContent(JacksonUtil.toJson(msgContentMsg));

        request.setMsg(msg);
        return request;
    }

    /**
     * 通过OA名查询真是的名字
     */
    private String getRealNameByOaNameWithCache(String oaName, String groupId) throws ExecutionException {
        return OANAME_TO_REAL_NAME_CACHE.get(Pair.of(groupId, oaName), () -> getRealNameByOaName(groupId, oaName));
    }

    private String getRealNameByOaName(String groupId, String oaName) {
        try {
            Map<String, Object> request = new HashMap<>();
            request.put("groupId", groupId);
            request.put("page", 1);
            request.put("pageSize", 100);
            request.put("requestId", getMsRequestId(this.appId, this.encryption, this.secret));

            HttpPost httpPost = new HttpPost(QUERY_GROUP_MEMBER_LIST_URL);
            httpPost.addHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
            httpPost.setEntity(new StringEntity(JacksonUtil.toJson(request), ContentType.APPLICATION_JSON));
            CloseableHttpResponse response = HTTP_CLIENT.execute(httpPost);
            String resStr = EntityUtils.toString(response.getEntity());
            Map<String, Object> jsonMap = JacksonUtil.ofJsonMap(resStr, HashMap.class, String.class, Object.class);
            Object data = jsonMap.get("data");
            if (data != null) {
                Collection<UserName> userNames = JacksonUtil.ofJsonCollection(JacksonUtil.toJson(data), ArrayList.class, UserName.class);
                for (UserName userName : userNames) {
                    if (StringUtils.equals(userName.getNickSpell(), oaName)) {
                        return userName.getNickName();
                    }
                }
            }
        } catch (Exception e) {
            log.error("getRealNameByOaName error", e);
        }
        return null;
    }

    // 请求响应的对象----------start--------------

    @Data
    private static class UserName {
        private String nickName;
        private String nickSpell;
    }


    @Data
    private static class Request {
        private MsRequestId msRequestId;
        private Msg msg;
    }

    @Data
    private static class MsRequestId {
        private String appId;
        private long reqTime;
        private String sign;
        private String trace;
        private String random;
    }

    @Data
    private static class Msg {
        private String content;
        private String senderId;
        private String showType;
        private String toId;
        private List<AtUser> atUsers;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class AtUser {
        private String oa;
        private int pos_start;
        private int pos_end;
        private String name;
    }

    @Data
    public static class Response {
        private String code;
        private String msg;

        public boolean success() {
            return StringUtils.equals("1", code);
        }
    }

    @Data
    public static class FunBaseParam {
        private String timestamp;
        private String random;
        private String signStr;
        private String appId;
    }

    // 请求响应的对象----------end--------------


    /**
     * md5加密
     */
    public static String md5(String data) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        md.update(data.getBytes());
        StringBuilder buf = new StringBuilder();
        byte[] bits = md.digest();
        for (int bit : bits) {
            int a = bit;
            if (a < 0) a += 256;
            if (a < 16) buf.append("0");
            buf.append(Integer.toHexString(a));
        }
        return buf.toString();
    }

    /**
     * sha1加密
     */
    public static String sha1(String data) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA1");
        md.update(data.getBytes());
        StringBuilder buf = new StringBuilder();
        byte[] bits = md.digest();
        for (int bit : bits) {
            int a = bit;
            if (a < 0) a += 256;
            if (a < 16) buf.append("0");
            buf.append(Integer.toHexString(a));
        }
        return buf.toString();
    }

    /**
     * 生成FunBaseParam
     *
     * @param encryption 申请appId时同时会提供encryption，取值是1或者2
     * @param secret     申请appId时同时会提供secret，是一个字符串
     * @return 鉴权参数
     * @throws Exception 加密异常
     */
    public static FunBaseParam getFunBaseParam(int encryption, String secret) throws Exception {
        FunBaseParam funBaseParam = new FunBaseParam();
        funBaseParam.setTimestamp(System.currentTimeMillis() + "");
        funBaseParam.setRandom("10");
        String signStr = null;
        if (encryption == 1) {
            signStr = md5(funBaseParam.getTimestamp() + secret + funBaseParam.getRandom());
        } else if (encryption == 2) {
            signStr = sha1(funBaseParam.getTimestamp() + secret + funBaseParam.getRandom());
        }
        funBaseParam.setSignStr(signStr);
        // 业务方的appId
        funBaseParam.setAppId("schedule");
        return funBaseParam;
    }

    public static MsRequestId getMsRequestId(String appId, int encryption, String secret) throws Exception {
        MsRequestId requestId = new MsRequestId();
        requestId.setReqTime(System.currentTimeMillis());
        requestId.setRandom(new Random().nextInt(1000000) + "");
        String signStr = null;
        if (encryption == 1) {
            signStr = md5(requestId.getReqTime() + secret + requestId.getRandom());
        } else if (encryption == 2) {
            signStr = sha1(requestId.getReqTime() + secret + requestId.getRandom());
        }
        requestId.setSign(signStr);
        requestId.setAppId(appId);
        requestId.setTrace(appId + ":" + requestId.getReqTime() + ":" + requestId.getRandom());
        return requestId;
    }


    public static class MeishiRobotBuilder {
        private String appId;
        private String secret;
        private int encryption;
        private String senderId;

        public MeishiRobot build() {
            return new MeishiRobot(this.appId, this.secret, this.encryption, this.senderId);
        }

        public MeishiRobotBuilder setAppId(String appId) {
            this.appId = appId;
            return this;
        }

        public MeishiRobotBuilder setSecret(String secret) {
            this.secret = secret;
            return this;
        }

        public MeishiRobotBuilder setEncryption(int encryption) {
            this.encryption = encryption;
            return this;
        }

        public MeishiRobotBuilder setSenderId(String senderId) {
            this.senderId = senderId;
            return this;
        }
    }

    public static MeishiRobotBuilder builder() {
        return new MeishiRobotBuilder();
    }


    public static void main(String[] args) {
        MeishiRobot robot = MeishiRobot.builder()
                .setAppId("ZhaopinEnterpriseRobot")
                .setEncryption(1)
                .setSecret("3db3e136236ea487919990d08499d168")
                .setSenderId("MIS_ROBOT_ZhaopinEnterpriseRobot")
                .build();

        // 查询机器人所在的群id
        // System.out.println(robot.queryRobotGroups());


        robot.sendTextMsg("251586", "test", ImmutableList.of("lupeng10"));
    }
}
